package com.huanzhidadi.ratelimiter.limiter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

/**
 * 令牌桶限流法
 * 持续速率是令牌发放速率
 * 瞬时最大速率是令牌桶的容量
 */
@Slf4j
@Component
@RefreshScope
public class TokenBucketRateLimiter implements RateLimiter {
    // 令牌桶的容量
    @Value("${limiter.tokenBucket.capacity}")
    private Integer capacity = 20;
    // 令牌发放速率
    @Value("${limiter.tokenBucket.generatedPerSecond}")
    private Integer generatedPerSecond = 5;
    // 最后一个令牌发放的时间
    private long lastTokenTime = System.currentTimeMillis();
    // 当前令牌数量
    private Integer currentTokens = 0;

    public TokenBucketRateLimiter() {}

    public TokenBucketRateLimiter(Integer capacity, Integer generatedPerSecond) {
        this.capacity = capacity;
        this.generatedPerSecond = generatedPerSecond;
    }

    @Override
    public synchronized boolean tryAcquire() {
        log.info("capacity:" + capacity);
        log.info("generatedPerSecond:" + generatedPerSecond);
        /*
          计算当前令牌的数量
          请求时间在最后令牌产生的时间相差大于等于1s
          1.重新计算令牌桶中的令牌数量
          2.将最后一个令牌发放时间重置为当前时间
         */
        long now = System.currentTimeMillis();
        //当前时间距离上次一访问时间之间产生了多少个令牌(now - lastTokenTime) / 1000 * generatedPerSecond
        //(now - lastTokenTime) / 1000这个是计算时间距离上次一访问时间间隔多少秒 generatedPerSecond代表的是每秒钟产生了多少令牌
        //newPermits代表的是在这段时间内产生的令牌数量
        //currentTokens当前的令牌数量 因为你的令牌数量肯定是要小于令牌桶的容量的
        //所以取currentTokens + newPermits, capacity小的值
        //然后更新时间把当前时间变成下一次时间
        //此时有一个请求来访问所以就相当于拿走一个令牌所以currentTokens-1即可
        //如果当前currentTokens没有数值就没有令牌直接禁止访问就完事了
        Integer timeGap = Math.toIntExact((now - lastTokenTime) / 1000);
        if (timeGap > 0) {
            log.info("timeGap: " + timeGap);
            Integer newPermits = timeGap * generatedPerSecond;
            currentTokens = Math.min(currentTokens + newPermits, capacity);
            lastTokenTime = now;
        }
        log.info("currentTokens: " + currentTokens);
        if (currentTokens > 0) {
            currentTokens--;
            return true;
        }
        return false;
    }
}
